from .constants import RotationType, Axis
from .auxiliary_methods import intersect, set2Decimal
import numpy as np
from decimal import Decimal
# required to plot a representation of Bin and contained items 
from matplotlib.patches import Rectangle,Circle
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.art3d as art3d
import copy

DEFAULT_NUMBER_OF_DECIMALS = 0
START_POSITION = [0, 0, 0]

def overlap(item1, item2, x=Axis.WIDTH, y=Axis.HEIGHT):
    # d1 = item1.getDimension()
    # d2 = item2.getDimension()

    d1 = item1.dimension
    d2 = item2.dimension

    cx1 = item1.position[x] + d1[x] / 2
    cy1 = item1.position[y] + d1[y] / 2
    cx2 = item2.position[x] + d2[x] / 2
    cy2 = item2.position[y] + d2[y] / 2

    ix = abs(cx1 - cx2)
    iy = abs(cy1 - cy2)

    overlap_x = (d1[x] + d2[x]) / 2 - ix
    overlap_y = (d1[y] + d2[y]) / 2 - iy

    if overlap_x > 0 and overlap_y > 0:
        overlap_area = overlap_x * overlap_y
    else:
        overlap_area = 0

    return overlap_area


# def overlap(item1, item2):
#     return overlap_area(item1, item2, Axis.WIDTH, Axis.HEIGHT)


# 创建控制台处理器并添加到日志记录器
# console_handler = logging.StreamHandler()
# console_handler.setLevel(logging.INFO)
#
# # 创建文件处理器并设置日志级别
# file_handler = logging.FileHandler('checklist.log')
# file_handler.setLevel(logging.INFO)
#
# # 创建格式化器并将其添加到处理器
# formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
# console_handler.setFormatter(formatter)
# file_handler.setFormatter(formatter)
#
# # 创建日志记录器并将处理器添加到记录器
# logger = logging.getLogger('')
# logger.setLevel(logging.ERROR)
# logger.addHandler(console_handler)
# logger.addHandler(file_handler)



class Item:

    def __init__(self, partno,name,typeof, WHD, weight, level, loadbear, updown, color):
        ''' '''
        self.partno = partno
        self.name = name
        self.typeof = typeof
        self.width = WHD[0]
        self.height = WHD[1]
        self.depth = WHD[2]
        self.weight = weight
        # Packing Priority level ,choose 1-3
        self.level = level
        # loadbear
        self.loadbear = loadbear
        # Upside down? True or False
        self.updown = updown if typeof == 'cube' else False
        # Draw item color
        self.color = color
        self.rotation_type = 0
        self.position = START_POSITION
        self.number_of_decimals = DEFAULT_NUMBER_OF_DECIMALS

        self.children = []  # 保存在item上方的其他items
        self.parents = {}   # 保存在item下方的其他items
        self.left = []  # 当前item左边的items(x轴左方)
        self.right = []  # 当前item右边的items(x轴右方)
        self.front = []  # 当前item前边的items(y轴前方)
        self.back = []  # 当前item后边的items(y轴后方)
        self.pressure = 0.  # 压力
        self.layer = 1  # 记录item处于z轴上的第几层
        self.dimension = [set2Decimal(s, 2) for s in list(WHD)]


    def formatNumbers(self, number_of_decimals):
        ''' '''
        self.width = set2Decimal(self.width, number_of_decimals)
        self.height = set2Decimal(self.height, number_of_decimals)
        self.depth = set2Decimal(self.depth, number_of_decimals)
        self.weight = set2Decimal(self.weight, number_of_decimals)
        self.number_of_decimals = number_of_decimals


    def string(self):
        ''' '''
        return "%s(%sx%sx%s, weight: %s) pos(%s) rt(%s) vol(%s)" % (
            self.partno, self.width, self.height, self.depth, self.weight,
            self.position, self.rotation_type, self.getVolume()
        )


    def getVolume(self):
        ''' '''
        return set2Decimal(self.width * self.height * self.depth, self.number_of_decimals)


    def getMaxArea(self):
        ''' '''
        a = sorted([self.width,self.height,self.depth],reverse=True) if self.updown == True else [self.width,self.height,self.depth]
    
        return set2Decimal(a[0] * a[1] , self.number_of_decimals)


    def setDimension(self):
        ''' rotation type '''
        if self.rotation_type == RotationType.RT_WHD:
            self.dimension = [self.width, self.height, self.depth]
        elif self.rotation_type == RotationType.RT_HWD:
            self.dimension = [self.height, self.width, self.depth]
        elif self.rotation_type == RotationType.RT_HDW:
            self.dimension = [self.height, self.depth, self.width]
        elif self.rotation_type == RotationType.RT_DHW:
            self.dimension = [self.depth, self.height, self.width]
        elif self.rotation_type == RotationType.RT_DWH:
            self.dimension = [self.depth, self.width, self.height]
        elif self.rotation_type == RotationType.RT_WDH:
            self.dimension = [self.width, self.depth, self.height]
        else:
            self.dimension = []


class Bin:

    def __init__(self, partno, WHD, max_weight,corner=0,put_type=1, price=0.):
        ''' '''
        self.partno = partno
        self.width = WHD[0]
        self.height = WHD[1]
        self.depth = WHD[2]
        self.max_weight = max_weight
        self.corner = corner
        self.items = []
        self.fit_items = np.array([[0,WHD[0],0,WHD[1],0,0]])
        self.unfitted_items = []
        self.number_of_decimals = DEFAULT_NUMBER_OF_DECIMALS
        self.fix_point = False
        self.check_stable = False
        self.support_surface_ratio = 0
        self.put_type = put_type
        # used to put gravity distribution
        self.gravity = []
        self.price = price

        self.is_over = False   # 标识车厢是否装满

    def formatNumbers(self, number_of_decimals):
        ''' '''
        self.width = set2Decimal(self.width, number_of_decimals)
        self.height = set2Decimal(self.height, number_of_decimals)
        self.depth = set2Decimal(self.depth, number_of_decimals)
        self.max_weight = set2Decimal(self.max_weight, number_of_decimals)
        self.number_of_decimals = number_of_decimals

    def string(self):
        ''' '''
        return "%s(%sx%sx%s, max_weight:%s) vol(%s)" % (
            self.partno, self.width, self.height, self.depth, self.max_weight,
            self.getVolume()
        )

    def getVolume(self):
        ''' '''
        return set2Decimal(
            self.width * self.height * self.depth, self.number_of_decimals
        )

    def getTotalWeight(self):
        ''' '''
        total_weight = 0

        for item in self.items:
            total_weight += item.weight

        return set2Decimal(total_weight, self.number_of_decimals)

    def getTotalVolume(self):
        ''' '''
        total_volume = 0
        for item in self.items:
            total_volume += item.width * item.height * item.depth
        return set2Decimal(total_volume, self.number_of_decimals)

    def decimal_range_intersection(self, start1, end1, start2, end2):
        intersection = max(0, min(end1, end2) - max(start1, start2))
        return intersection

    def count_rows_with_value(self, matrix, value):
        count = 0
        for sublist in matrix:
            if any(num.level == value for num in sublist):
                count += 1
        return count

    def get_rows_from_value(self, matrix, value):
        rows_indices = []
        found = False

        for i, sublist in enumerate(matrix):
            if found or any(num.level == value for num in sublist):
                found = True
                rows_indices.append(i)

        return [matrix[i] for i in rows_indices]

    def putItem4cube(self, item, pivot, axis=None):
        ''' put item in bin '''

        fit = False
        valid_item_position = item.position
        item.position = pivot
        rotate = RotationType.ALL if item.updown == True else RotationType.Notupdown
        # 初始化堆叠的列表
        stack_items = []

        for i in range(0, len(rotate)):
            item.rotation_type = i
            item.setDimension()
            dimension = item.dimension
            # rotate
            if (
                    self.width < pivot[0] + dimension[0] or
                    self.height < pivot[1] + dimension[1] or
                    self.depth < pivot[2] + dimension[2]
            ):
                continue
            # 判断是否有交叠
            fit = True

            for j, current_item_in_bin in enumerate(self.items):
                if intersect(current_item_in_bin, item):
                    fit = False
                    break

                olp = overlap(current_item_in_bin, item)
                # 排除尺寸不一致的木箱
                # 找到当前item下面的item并且都是item
                if current_item_in_bin.position[-1] < item.position[-1] and (current_item_in_bin.level == 1) and (
                        item.level == 1) and olp > 0:
                    # 如果装箱上面和下面面积不相等则跳过
                    if (olp != item.width * item.height) or (olp != current_item_in_bin.width * current_item_in_bin.height):
                        # logging.info("木箱尺寸一致性校验")
                        fit = False
                        break
                # 重不压轻校验
                if current_item_in_bin.position[-1] < item.position[-1] and olp > 0:
                    if current_item_in_bin.weight < item.weight:
                        # logging.info("不同货物重不压轻校验")
                        fit = False
                        break

                # 通过交叠面积来进行item的堆叠
                if current_item_in_bin.position[-1] < item.position[-1] and olp > 0:
                    stack_items.append(current_item_in_bin)

            # 构建当前item下面的序列
            stack_items.append(item)

            # 筛选出堆叠的最小模块（目前仅考虑最底层）
            if len(stack_items) > 1:
                # 将当前item下面的item按高度排列[[...],[...],[...],..]
                stack_items = [[item for item in stack_items if item.position[-1] == depth] for depth in
                               sorted(list(set(item.position[-1] for item in stack_items)))]

                # 如果是最底层
                for m in range(len(stack_items)):
                    for n in range(len(stack_items[m])):
                        # 初始化up_items
                        up_items = []
                        stack_items[m][n].up_items_weight = 0
                        # 获取最底层上面堆叠的所有item以及重量
                        for current_item_in_bin in self.items:
                            # olp1 = overlap(stack_items[m][n], current_item_in_bin)
                            if current_item_in_bin.position[-1] > stack_items[m][n].position[-1] and overlap(stack_items[m][n], current_item_in_bin) > 0:
                                up_items.append(current_item_in_bin)
                                stack_items[m][n].up_items_weight += (overlap(stack_items[m][n], current_item_in_bin) / (
                                                                                  current_item_in_bin.width * current_item_in_bin.height)) * current_item_in_bin.weight

                        olp2 = overlap(stack_items[m][n], item)
                        stack_items[m][n].up_items_weight = stack_items[m][n].up_items_weight + (
                                    olp2 / (item.width * item.height)) * item.weight
                        up_items.append(item)

                        # 只考虑木箱
                        # 只考虑最下层的情况（优先级1）
                        # 判断最底层是否是重货或者是泡货
                        cargo_density = stack_items[m][n].weight / (
                                    stack_items[m][n].width * stack_items[m][n].height * stack_items[m][n].depth)
                        num_wooden = self.count_rows_with_value(stack_items[m:], 1)
                        # 只做最基础判断(如果是木箱并且重量低于60只允许在最上面)
                        if stack_items[m][n].level == 1 and stack_items[m][n].weight <= 60:
                            if num_wooden > 1 or stack_items[m][n].up_items_weight > 1200:
                                # logging.info("木箱低于60KG不能堆叠校验")
                                fit = False
                                break
                        # 对于重量小于600的木箱重货，如果出现1、堆叠层数超过3层 2、堆叠重量超过1200 3、堆叠重量超过自身重量
                        if stack_items[m][n].level == 1 and cargo_density > 330 and stack_items[m][n].weight <= 600:
                            if num_wooden > 3 or stack_items[m][n].up_items_weight > 1200:
                                # logging.info("重货木箱三层堆叠校验")
                                fit = False
                                break
                        # 对于重量大于600小于1200的木箱重货，如果出现1、堆叠层数超过2层 2、堆叠重量超过1200 3、堆叠重量超过自身重量
                        if stack_items[m][n].level == 1 and cargo_density > 330 and stack_items[m][n].weight > 600 and \
                                stack_items[m][n].weight < 1200:
                            if num_wooden > 2 or stack_items[m][n].up_items_weight > 1200:
                                # logging.info("重货木箱两层堆叠校验")
                                fit = False
                                break
                        # 对于重量大于1200的木箱重货
                        if stack_items[m][n].level == 1 and stack_items[m][n].weight > 1200:
                            if num_wooden > 1 or stack_items[m][n].up_items_weight > 1200:
                                # logging.info("重货木箱不能堆叠校验")
                                fit = False
                                break

                        # 进一步考虑托盘
                        # 对于托盘面积<=0.6m^2的不可以堆叠
                        num_tray = self.count_rows_with_value(stack_items[m:], 2)
                        if stack_items[m][n].level == 2 and stack_items[m][n].width * stack_items[m][n].height <= 0.6:
                            if num_tray > 1 or stack_items[m][n].up_items_weight > 600:
                                # logging.info("托盘小于0.6m^2不能堆叠校验")
                                fit = False
                                break

                        # 对于托盘重量小于100的不可以堆叠
                        if stack_items[m][n].level == 2 and stack_items[m][n].weight < 100:
                            if num_tray > 1 or stack_items[m][n].up_items_weight > 600:
                                # logging.info("托盘小于100kg不能堆叠校验")
                                fit = False
                                break

                        # 对于托盘重量大于等于600的不可以堆叠
                        if stack_items[m][n].level == 2 and stack_items[m][n].weight >= 600:
                            if num_tray > 1 or stack_items[m][n].up_items_weight > 600:
                                # logging.info("托盘大于600kg不能堆叠校验")
                                fit = False
                                break
                        if fit:
                            stack_items[m][n].pressure = float(stack_items[m][n].up_items_weight)
                            item.pressure = 0.
                        else:
                            stack_items[m][n].pressure = float(stack_items[m][n].up_items_weight - (
                                        olp2 / (item.width * item.height)) * item.weight)

            if fit:
                # cal total weight
                if self.getTotalWeight() + item.weight > self.max_weight:
                    fit = False
                    return fit

                if self.getTotalVolume() + set2Decimal(item.width * item.height * item.depth,
                                                       self.number_of_decimals) > self.width * self.height * self.depth * Decimal(
                        0.9):
                    # logging.info("纸箱总体积不超过货车体积90%校验")
                    fit = False
                    return fit

                # fix point float prob
                if self.fix_point == True:

                    [w, h, d] = dimension
                    [x, y, z] = [float(pivot[0]), float(pivot[1]), float(pivot[2])]

                    # for i in range(3):
                    #     # fix height
                    #     y = self.checkHeight([x, x + float(w), y, y + float(h), z, z + float(d)])
                    #     # fix width
                    #     x = self.checkWidth([x, x + float(w), y, y + float(h), z, z + float(d)])
                    #     # fix depth
                    #     z = self.checkDepth([x, x + float(w), y, y + float(h), z, z + float(d)])

                    # check stability on item
                    # rule :
                    # 1. Define a support ratio, if the ratio below the support surface does not exceed this ratio, compare the second rule.
                    # 2. If there is no support under any vertices of the bottom of the item, then fit = False.
                    if self.check_stable == True:
                        # Cal the surface area of ​​item.
                        item_area_lower = float(dimension[0] * dimension[1])
                        # Cal the surface area of ​​the underlying support.
                        support_area_upper = 0
                        for i in self.fit_items:
                            # Verify that the lower support surface area is greater than the upper support surface area * support_surface_ratio.
                            # if z == i[5]:
                            #     area = len(set([j for j in range(int(x), int(x + int(w)))]) & set(
                            #         [j for j in range(int(i[0]), int(i[1]))])) * \
                            #            len(set([j for j in range(int(y), int(y + int(h)))]) & set(
                            #                [j for j in range(int(i[2]), int(i[3]))]))
                            #     support_area_upper += area
                            if z == i[5]:
                                w = float(w)
                                h = float(h)
                                area_x = self.decimal_range_intersection(x, x + w, i[0], i[1])
                                area_y = self.decimal_range_intersection(y, y + h, i[2], i[3])
                                area = area_x * area_y
                                support_area_upper += area

                        # If not , get four vertices of the bottom of the item.
                        if float(support_area_upper) / item_area_lower < self.support_surface_ratio:
                            four_vertices = [[x, y], [x + float(w), y], [x, y + float(h)], [x + float(w), y + float(h)]]
                            #  If any vertices is not supported, fit = False.
                            c = [False, False, False, False]
                            for i in self.fit_items:
                                if z == i[5]:
                                    for jdx, j in enumerate(four_vertices):
                                        if (i[0] <= j[0] <= i[1]) and (i[2] <= j[1] <= i[3]):
                                            c[jdx] = True
                            if False in c:
                                item.position = valid_item_position
                                fit = False
                                return fit

                    self.fit_items = np.append(self.fit_items,
                                               np.array([[x, x + float(w), y, y + float(h), z, z + float(d)]]), axis=0)

                    item.position = [set2Decimal(x, self.number_of_decimals), set2Decimal(y, self.number_of_decimals),
                                     set2Decimal(z, self.number_of_decimals)]

                if fit:
                    self.items.append(copy.deepcopy(item))


            else:
                item.position = valid_item_position

            return fit

        else:
            item.position = valid_item_position

        return fit

    def putItem4heap(self, item, pivot, axis=None):
        # print('putItem', item.dimension)
        # print('BinSize', self.width, self.height, self.depth)
        ''' put item in bin '''
        fit = False
        valid_item_position = item.position
        item.position = pivot

        rotate = [RotationType.RT_WHD]

        for i in range(0, len(rotate)):
            item.rotation_type = i
            # dimension = item.getDimension()
            dimension = item.dimension
            # rotatate
            if (
                    self.width < pivot[0] + dimension[0] or
                    self.height < pivot[1] + dimension[1] or
                    self.depth < pivot[2] + dimension[2]
            ):
                continue

            fit = True

            for current_item_in_bin in self.items:
                intersect_flag = intersect(current_item_in_bin, item)
                if intersect_flag:
                    fit = False
                    break
            if fit:
                # cal total weight
                if self.getTotalWeight() + item.weight > self.max_weight:
                    fit = False
                    return fit

                # fix point float prob
                if self.fix_point == True:

                    [w, h, d] = dimension
                    [x, y, z] = [float(pivot[0]), float(pivot[1]), float(pivot[2])]

                    for i in range(3):
                        # fix height
                        # y = self.checkHeight([x,x+float(w),y,y+float(h),z,z+float(d)])
                        # fix width
                        # x = self.checkWidth([x,x+float(w),y,y+float(h),z,z+float(d)])
                        # fix depth
                        z = self.checkDepth([x, x + float(w), y, y + float(h), z, z + float(d)])

                    # check stability on item
                    # rule :
                    # 1. Define a support ratio, if the ratio below the support surface does not exceed this ratio, compare the second rule.
                    # 2. If there is no support under any vertices of the bottom of the item, then fit = False.
                    if self.check_stable == True:
                        # Cal the surface area of ​​item.
                        item_area_lower = int(dimension[0] * dimension[1])
                        # Cal the surface area of ​​the underlying support.
                        support_area_upper = 0
                        for i in self.fit_items:
                            # Verify that the lower support surface area is greater than the upper support surface area * support_surface_ratio.
                            if z == i[5]:
                                area = len(set([j for j in range(int(x), int(x + int(w)))]) & set(
                                    [j for j in range(int(i[0]), int(i[1]))])) * \
                                       len(set([j for j in range(int(y), int(y + int(h)))]) & set(
                                           [j for j in range(int(i[2]), int(i[3]))]))
                                support_area_upper += area

                        # If not , get four vertices of the bottom of the item.
                        if item.typeof == "cube" and support_area_upper / item_area_lower < self.support_surface_ratio:
                            four_vertices = [[x, y], [x + float(w), y], [x, y + float(h)], [x + float(w), y + float(h)]]
                            #  If any vertices is not supported, fit = False.
                            c = [False, False, False, False]
                            for i in self.fit_items:
                                if z == i[5]:
                                    for jdx, j in enumerate(four_vertices):
                                        if (i[0] <= j[0] <= i[1]) and (i[2] <= j[1] <= i[3]):
                                            c[jdx] = True
                            if False in c:
                                item.position = valid_item_position
                                fit = False
                                return fit

                    self.fit_items = np.append(self.fit_items,
                                               np.array([[x, x + float(w), y, y + float(h), z, z + float(d)]]), axis=0)
                    item.position = [set2Decimal(x), set2Decimal(y), set2Decimal(z)]

                if fit:
                    self.items.append(copy.deepcopy(item))

            else:
                item.position = valid_item_position

            return fit

        else:
            item.position = valid_item_position

        return fit

    def putItem(self, item, pivot, axis=None):
        if item.typeof == 'cube':
            return self.putItem4cube(item, pivot, axis=axis)
        elif item.typeof == 'heap':
            return self.putItem4heap(item, pivot, axis=axis)

    def checkDepth(self,unfix_point):
        ''' fix item position z '''
        z_ = [[0,0],[float(self.depth),float(self.depth)]]
        for j in self.fit_items:
            # creat x set
            x_bottom = set([i for i in range(int(j[0]),int(j[1]))])
            x_top = set([i for i in range(int(unfix_point[0]),int(unfix_point[1]))])
            # creat y set
            y_bottom = set([i for i in range(int(j[2]),int(j[3]))])
            y_top = set([i for i in range(int(unfix_point[2]),int(unfix_point[3]))])
            # find intersection on x set and y set.
            if len(x_bottom & x_top) != 0 and len(y_bottom & y_top) != 0 :
                z_.append([float(j[4]),float(j[5])])
        top_depth = unfix_point[5] - unfix_point[4]
        # find diff set on z_.
        z_ = sorted(z_, key = lambda z_ : z_[1])
        for j in range(len(z_)-1):
            if z_[j+1][0] -z_[j][1] >= top_depth:
                return z_[j][1]
        return unfix_point[4]

    def checkWidth(self,unfix_point):
        ''' fix item position x ''' 
        x_ = [[0,0],[float(self.width),float(self.width)]]
        for j in self.fit_items:
            # creat z set
            z_bottom = set([i for i in range(int(j[4]),int(j[5]))])
            z_top = set([i for i in range(int(unfix_point[4]),int(unfix_point[5]))])
            # creat y set
            y_bottom = set([i for i in range(int(j[2]),int(j[3]))])
            y_top = set([i for i in range(int(unfix_point[2]),int(unfix_point[3]))])
            # find intersection on z set and y set.
            if len(z_bottom & z_top) != 0 and len(y_bottom & y_top) != 0 :
                x_.append([float(j[0]),float(j[1])])
        top_width = unfix_point[1] - unfix_point[0]
        # find diff set on x_bottom and x_top.
        x_ = sorted(x_,key = lambda x_ : x_[1])
        for j in range(len(x_)-1):
            if x_[j+1][0] -x_[j][1] >= top_width:
                return x_[j][1]
        return unfix_point[0]

    def checkHeight(self,unfix_point):
        '''fix item position y '''
        y_ = [[0,0],[float(self.height),float(self.height)]]
        for j in self.fit_items:
            # creat x set
            x_bottom = set([i for i in range(int(j[0]),int(j[1]))])
            x_top = set([i for i in range(int(unfix_point[0]),int(unfix_point[1]))])
            # creat z set
            z_bottom = set([i for i in range(int(j[4]),int(j[5]))])
            z_top = set([i for i in range(int(unfix_point[4]),int(unfix_point[5]))])
            # find intersection on x set and z set.
            if len(x_bottom & x_top) != 0 and len(z_bottom & z_top) != 0 :
                y_.append([float(j[2]),float(j[3])])
        top_height = unfix_point[3] - unfix_point[2]
        # find diff set on y_bottom and y_top.
        y_ = sorted(y_,key = lambda y_ : y_[1])
        for j in range(len(y_)-1):
            if y_[j+1][0] -y_[j][1] >= top_height:
                return y_[j][1]

        return unfix_point[2]

    def addCorner(self):
        '''add container coner '''
        if self.corner != 0 :
            corner = set2Decimal(self.corner)
            corner_list = []
            for i in range(8):
                a = Item(
                    partno='corner{}'.format(i),
                    name='corner', 
                    typeof='cube',
                    WHD=(corner,corner,corner), 
                    weight=0, 
                    level=0, 
                    loadbear=0, 
                    updown=True, 
                    color='#000000')

                corner_list.append(a)
            return corner_list

    def putCorner(self,info,item):
        '''put coner in bin '''
        fit = False
        x = set2Decimal(self.width - self.corner)
        y = set2Decimal(self.height - self.corner)
        z = set2Decimal(self.depth - self.corner)
        pos = [[0,0,0],[0,0,z],[0,y,z],[0,y,0],[x,y,0],[x,0,0],[x,0,z],[x,y,z]]
        item.position = pos[info]
        self.items.append(item)

        corner = [float(item.position[0]),float(item.position[0])+float(self.corner),float(item.position[1]),float(item.position[1])+float(self.corner),float(item.position[2]),float(item.position[2])+float(self.corner)]

        self.fit_items = np.append(self.fit_items,np.array([corner]),axis=0)
        return

    def clearBin(self):
        ''' clear item which in bin '''
        self.items = []
        self.fit_items = np.array([[0,self.width,0,self.height,0,0]])
        return


class Packer:

    def __init__(self):
        ''' '''
        self.bins = []
        self.items = []
        self.unfit_items = []
        self.total_items = 0
        self.binding = []
        # self.apex = []


    def addBin(self, bin):
        ''' '''
        return self.bins.append(bin)


    def addItem(self, item):
        ''' '''
        self.total_items = len(self.items) + 1

        return self.items.append(item)

    def pack2Bin(self, bin, item,fix_point,check_stable,support_surface_ratio):
        ''' pack item to bin '''

        temp_bin = copy.deepcopy(bin)

        if item.typeof == 'heap':
            item.depth = bin.depth

        fitted = False
        bin.fix_point = fix_point
        bin.check_stable = check_stable
        bin.support_surface_ratio = support_surface_ratio

        # first put item on (0,0,0) , if corner exist ,first add corner in box.
        if bin.corner != 0 and not bin.items:
            corner_lst = bin.addCorner()
            for i in range(len(corner_lst)) :
                bin.putCorner(i,corner_lst[i])

        elif not bin.items:
            response = bin.putItem(item, item.position)

            if not response:
                bin.unfitted_items.append(item)
                return False
            return True

        # 设置堆叠顺序长:0；宽:1; 高:2；
        for axis in [2, 1, 0]:
            items_in_bin = bin.items
            total_items = len(items_in_bin)
            for kn, ib in enumerate(items_in_bin):

                if kn >= total_items:
                    break

                pivot = [0, 0, 0]
                # w, h, d = ib.getDimension()
                w, h, d = ib.dimension
                if axis == Axis.WIDTH:
                    pivot = [ib.position[0] + w,ib.position[1],ib.position[2]]
                elif axis == Axis.HEIGHT:
                    pivot = [ib.position[0],ib.position[1] + h,ib.position[2]]
                elif axis == Axis.DEPTH:
                    pivot = [ib.position[0],ib.position[1],ib.position[2] + d]

                # 按照上述堆叠顺序获取item以及item的位置和维度信息
                put_flag = bin.putItem(item, pivot, axis)
                if put_flag:
                    if not self.refreshPressure(bin, item):
                        # 如果是在z轴上进行堆叠，则需要检查所有item的压力是否都在阈值内
                        bin.items = copy.deepcopy(temp_bin.items)
                        bin.fit_items = copy.deepcopy(temp_bin.fit_items)
                        bin.unfitted_items = copy.deepcopy(temp_bin.unfitted_items)
                        fitted = False
                    else:
                        fitted = True
                        break
            if fitted:
                break
        if not fitted:
            bin.unfitted_items.append(item)
            return False
        return True

    def pack2bin(self, bin, item, fix_point, check_stable, support_surface_ratio):
        ''' pack item to bin '''
        fitted = False
        bin.fix_point = fix_point
        bin.check_stable = check_stable
        bin.support_surface_ratio = support_surface_ratio
        # first put item on (0,0,0) , if corner exist ,first add corner in box.
        if bin.corner != 0 and not bin.items:
            corner_lst = bin.addCorner()
            for i in range(len(corner_lst)):
                bin.putCorner(i, corner_lst[i])

        elif not bin.items:
            response = bin.putItem(item, [Decimal(0), Decimal(0), Decimal(0)], item.position)

            if not response:
                bin.unfitted_items.append(item)
                return False
            return True

        # for axis in [1, 0, 2]:
        for axis in [1, 2, 0]:
            items_in_bin = bin.items
            for index, ib in enumerate(items_in_bin):

                if axis == 0 and ib.right:
                    continue
                elif axis == 1 and ib.front:
                    continue
                elif axis == 2 and ib.children:
                    continue

                pivot = [0, 0, 0]
                # w, h, d = ib.getDimension()
                w, h, d = ib.dimension
                if axis == Axis.WIDTH:
                    pivot = [ib.position[0] + w, ib.position[1], ib.position[2]]
                elif axis == Axis.HEIGHT:
                    pivot = [ib.position[0], ib.position[1] + h, ib.position[2]]
                elif axis == Axis.DEPTH:
                    pivot = [ib.position[0], ib.position[1], ib.position[2] + d]

                if bin.putItem(item, pivot, axis):

                    if axis == 0:
                        bin.items[index].right.append(len(bin.items) - 1)
                        bin.items[-1].left.append(index)
                    elif axis == 1:
                        bin.items[index].front.append(len(bin.items) - 1)
                        bin.items[-1].back.append(index)
                    elif axis == 2:
                        bin.items[index].children.append(len(bin.items) - 1)

                    fitted = True
                    break

            if fitted:
                break
        if not fitted:
            bin.unfitted_items.append(item)
            return False
        return True

    def sortBinding(self,bin):
        ''' sorted by binding '''
        b,front,back = [],[],[]
        for i in range(len(self.binding)):
            b.append([]) 
            for item in self.items:
                if item.name in self.binding[i]:
                    b[i].append(item)
                elif item.name not in self.binding:
                    if len(b[0]) == 0 and item not in front:
                        front.append(item)
                    elif item not in back and item not in front:
                        back.append(item)

        min_c = min([len(i) for i in b])
        
        sort_bind =[]
        for i in range(min_c):
            for j in range(len(b)):
                sort_bind.append(b[j][i])
        
        for i in b:
            for j in i:
                if j not in sort_bind:
                    self.unfit_items.append(j)

        self.items = front + sort_bind + back
        return

    def putOrder(self):
        '''Arrange the order of items '''
        r = []
        for i in self.bins:
            # open top container
            if i.put_type == 2:
                i.items.sort(key=lambda item: item.position[0], reverse=False)
                i.items.sort(key=lambda item: item.position[1], reverse=False)
                i.items.sort(key=lambda item: item.position[2], reverse=False)
            # general container
            elif i.put_type == 1:
                i.items.sort(key=lambda item: item.position[1], reverse=False)
                i.items.sort(key=lambda item: item.position[2], reverse=False)
                i.items.sort(key=lambda item: item.position[0], reverse=False)
            else :
                pass
        return

    def gravityCenter(self,bin):
        ''' 
        Deviation Of Cargo gravity distribution
        ''' 
        w = int(bin.width)
        h = int(bin.height)
        d = int(bin.depth)

        area1 = [set(range(0,w//2+1)),set(range(0,h//2+1)),0]
        area2 = [set(range(w//2+1,w+1)),set(range(0,h//2+1)),0]
        area3 = [set(range(0,w//2+1)),set(range(h//2+1,h+1)),0]
        area4 = [set(range(w//2+1,w+1)),set(range(h//2+1,h+1)),0]
        area = [area1,area2,area3,area4]

        for i in bin.items:

            x_st = int(i.position[0])
            y_st = int(i.position[1])
            if i.rotation_type == 0:
                x_ed = int(i.position[0] + i.width)
                y_ed = int(i.position[1] + i.height)
            elif i.rotation_type == 1:
                x_ed = int(i.position[0] + i.height)
                y_ed = int(i.position[1] + i.width)
            elif i.rotation_type == 2:
                x_ed = int(i.position[0] + i.height)
                y_ed = int(i.position[1] + i.depth)
            elif i.rotation_type == 3:
                x_ed = int(i.position[0] + i.depth)
                y_ed = int(i.position[1] + i.height)
            elif i.rotation_type == 4:
                x_ed = int(i.position[0] + i.depth)
                y_ed = int(i.position[1] + i.width)
            elif i.rotation_type == 5:
                x_ed = int(i.position[0] + i.width)
                y_ed = int(i.position[1] + i.depth)

            x_set = set(range(x_st,int(x_ed)+1))
            y_set = set(range(y_st,y_ed+1))

            # cal gravity distribution
            for j in range(len(area)):
                if x_set.issubset(area[j][0]) and y_set.issubset(area[j][1]) : 
                    area[j][2] += int(i.weight)
                    break
                # include x and !include y
                elif x_set.issubset(area[j][0]) == True and y_set.issubset(area[j][1]) == False and len(y_set & area[j][1]) != 0 : 
                    y = len(y_set & area[j][1]) / (y_ed - y_st) * int(i.weight)
                    area[j][2] += y
                    if j >= 2 :
                        area[j-2][2] += (int(i.weight) - x)
                    else :
                        area[j+2][2] += (int(i.weight) - y)
                    break
                # include y and !include x
                elif x_set.issubset(area[j][0]) == False and y_set.issubset(area[j][1]) == True and len(x_set & area[j][0]) != 0 : 
                    x = len(x_set & area[j][0]) / (x_ed - x_st) * int(i.weight)
                    area[j][2] += x
                    if j >= 2 :
                        area[j-2][2] += (int(i.weight) - x)
                    else :
                        area[j+2][2] += (int(i.weight) - x)
                    break
                # !include x and !include y
                elif x_set.issubset(area[j][0])== False and y_set.issubset(area[j][1]) == False and len(y_set & area[j][1]) != 0  and len(x_set & area[j][0]) != 0 :
                    all = (y_ed - y_st) * (x_ed - x_st)
                    y = len(y_set & area[0][1])
                    y_2 = y_ed - y_st - y
                    x = len(x_set & area[0][0])
                    x_2 = x_ed - x_st - x
                    area[0][2] += x * y / all * int(i.weight)
                    area[1][2] += x_2 * y / all * int(i.weight)
                    area[2][2] += x * y_2 / all * int(i.weight)
                    area[3][2] += x_2 * y_2 / all * int(i.weight)
                    break
            
        r = [area[0][2],area[1][2],area[2][2],area[3][2]]
        result = []
        for i in r :
            result.append(round(i / sum(r) * 100,2))
        return result

    def packCube(self, bigger_first=False, distribute_items=True, fix_point=True, check_stable=True,
             support_surface_ratio=0.75, binding=[], number_of_decimals=DEFAULT_NUMBER_OF_DECIMALS):
        '''pack master func '''
        # set decimals
        for bin in self.bins:
            bin.formatNumbers(number_of_decimals)

        for item in self.items:
            item.formatNumbers(number_of_decimals)
        # add binding attribute
        self.binding = binding
        # Bin : sorted by volumn
        self.bins.sort(key=lambda bin: bin.getVolume(), reverse=bigger_first)
        # self.bins.sort(key=lambda bin: bin.getTotalVolume() / bin.getVolume(), reverse=False)
        # Item : sorted by volumn -> sorted by loadbear -> sorted by level -> binding
        self.items.sort(key=lambda item: item.getVolume(), reverse=True)
        # self.items.sort(key=lambda item: item.getMaxArea(), reverse=bigger_first)
        # self.items.sort(key=lambda item: item.loadbear, reverse=True)
        self.items.sort(key=lambda item: item.level, reverse=False)
        # self.items.sort(key=lambda item: item.weight, reverse=True)

        # sorted by binding
        if binding != []:
            self.sortBinding(bin)

        # for idx, bin in enumerate(self.bins):
        #     # pack item to bin
        #     for item in self.items:
        #         self.pack2bin(bin, item, fix_point, check_stable, support_surface_ratio)

        for n, item in enumerate(self.items):
            # if n >= 60:
            #     print(n)
            for idx, bin in enumerate(self.bins):
                if bin.is_over:
                    continue
                flag = self.pack2bin(bin, item, fix_point, check_stable, support_surface_ratio)
                if flag:  # 若装进去了，则继续装下一个item
                    break
                else:  # 若没装进去，则换下一个下一个箱子装
                    continue

            if binding != []:
                # resorted
                self.items.sort(key=lambda item: item.getVolume(), reverse=bigger_first)
                self.items.sort(key=lambda item: item.loadbear, reverse=True)
                self.items.sort(key=lambda item: item.level, reverse=False)
                # clear bin
                bin.items = []
                bin.unfitted_items = self.unfit_items
                bin.fit_items = np.array([[0, bin.width, 0, bin.height, 0, 0]])

                # repacking
                for item in self.items:
                    self.pack2Bin(bin, item, fix_point, check_stable, support_surface_ratio)

            # Deviation Of Cargo Gravity Center
            # self.bins[idx].gravity = self.gravityCenter(bin)

            if distribute_items:
                for bitem in bin.items:
                    no = bitem.partno
                    for item in self.items:
                        if item.partno == no:
                            self.items.remove(item)
                            break

        # put order of items
        self.putOrder()

        if self.items != []:
            self.unfit_items = copy.deepcopy(self.items)
            self.items = []
        # for item in self.items.copy():
        #     if item in bin.unfitted_items:
        #         self.items.remove(item)

    def packCylinder(self, bigger_first=False,distribute_items=True,fix_point=True,check_stable=True,support_surface_ratio=0.75,binding=[],number_of_decimals=DEFAULT_NUMBER_OF_DECIMALS):
        # 对Cylinder组成的heaps进行装箱
        # set decimals
        for bin in self.bins:
            bin.formatNumbers(number_of_decimals)

        for item in self.items:
            item.formatNumbers(number_of_decimals)
        # add binding attribute
        self.binding = binding
        # Bin : sorted by volumn
        self.bins.sort(key=lambda bin: bin.getVolume(), reverse=bigger_first)
        # Item : sorted by volumn -> sorted by loadbear -> sorted by level -> binding
        self.items.sort(key=lambda item: item.getVolume(), reverse=bigger_first)
        # self.items.sort(key=lambda item: item.getMaxArea(), reverse=bigger_first)
        self.items.sort(key=lambda item: item.loadbear, reverse=True)
        self.items.sort(key=lambda item: item.level, reverse=False)
        # sorted by binding
        if binding != []:
            self.sortBinding(bin)
        # for idx, bin in enumerate(self.bins):
        #     # pack item to bin
        #     for n, item in enumerate(self.items):
        #         # if item.typeof != 'heap':
        #         #     continue
        #         self.pack2Bin(bin, item, fix_point, check_stable, support_surface_ratio)

        for n, item in enumerate(self.items):
            for idx, bin in enumerate(self.bins):
                flag = self.pack2Bin(bin, item, fix_point, check_stable, support_surface_ratio)
                if flag:  # 若装进去了，则继续装下一个item
                    break
                else:   # 若没装进去，则换下一个下一个箱子装
                    continue

            if binding != []:
                # resorted
                self.items.sort(key=lambda item: item.getVolume(), reverse=bigger_first)
                self.items.sort(key=lambda item: item.loadbear, reverse=True)
                self.items.sort(key=lambda item: item.level, reverse=False)
                # clear bin
                bin.items = []
                bin.unfitted_items = self.unfit_items
                bin.fit_items = np.array([[0, bin.width, 0, bin.height, 0, 0]])
                # repacking
                for item in self.items:
                    self.pack2Bin(bin, item, fix_point, check_stable, support_surface_ratio)

            # Deviation Of Cargo Gravity Center
            self.bins[idx].gravity = 0.

            if distribute_items:
                for bitem in bin.items:
                    no = bitem.partno
                    for item in self.items:
                        if item.partno == no:
                            self.items.remove(item)
                            break

        # put order of items
        self.putOrder()

        if self.items != []:
            self.unfit_items = copy.deepcopy(self.items)
            self.items = []
        # for item in self.items.copy():
        #     if item in bin.unfitted_items:
        #         self.items.remove(item)


    def pack(self, bigger_first=False,distribute_items=True,fix_point=True,check_stable=True,support_surface_ratio=0.75,binding=[],number_of_decimals=DEFAULT_NUMBER_OF_DECIMALS):
        # 先对cylinder组成的heap进行装箱，再对剩余的cube进行装箱
        cylinder_items = copy.deepcopy(list(filter(lambda x: x.typeof == 'heap', self.items)))
        cube_items = copy.deepcopy(list(filter(lambda x: x.typeof == 'cube', self.items)))

        # 1. 打包heaps
        self.items = cylinder_items
        # self.total_items = len(cylinder_items)
        self.packCylinder(bigger_first,distribute_items,fix_point,check_stable,support_surface_ratio,binding,number_of_decimals)
        # self.bins[0].items.append()
        # 2. 打包cubes
        self.items = cube_items
        # self.total_items = len(cylinder_items) + len(cube_items)
        self.packCube(bigger_first,distribute_items,fix_point,check_stable,support_surface_ratio,binding,number_of_decimals)



        # self.packCube(bigger_first, distribute_items, fix_point, check_stable, support_surface_ratio, binding,
        #               number_of_decimals)



        # self.packCylinder(bigger_first, distribute_items, fix_point, check_stable, support_surface_ratio, binding, number_of_decimals)

    def calculatePressureArea(self, item1, item2):
        """
        计算item1与item2的受力面积(item1在下面，item2在上面)
        :param item1:
        :param item2:
        :return:
        """
        x1, y1, z1 = item1.position
        x1, y1, z1 = float(x1), float(y1), float(z1)
        x2, y2, z2 = item2.position
        x2, y2, z2 = float(x2), float(y2), float(z2)
        w1, h1, d1 = float(item1.width), float(item1.height), float(item1.depth)
        w2, h2, d2 = float(item2.width), float(item2.height), float(item2.depth)

        x_min1, x_max1 = x1, x1 + w1
        y_min1, y_max1 = y1, y1 + h1
        x_min2, x_max2 = x2, x2 + w2
        y_min2, y_max2 = y2, y2 + h2

        # 两个矩形交集部分的坐标
        x_min, x_max = max(x_min1, x_min2), min(x_max1, x_max2)
        y_min, y_max = max(y_min1, y_min2), min(y_max1, y_max2)

        # if x1 > x2:
        #     x1, x2 = x2, x1
        #     w1, w2 = w2, w1
        # if y1 > y2:
        #     y1, y2 = y2, y1
        #     h1, h2 = h2, h1

        # 1. 判断item1和item2在z轴上是否有交集
        if z1 + d1 != z2:
            return 0
        # # 2. 计算item1和item2在xoy面上的交集
        # if x1 + w1 > x2 and y2 + h2 > y1:
        #     # 2.1 计算重叠部分面积(即受力面积)
        #     return (x1 + w1 - x2) * (y2 + h2 - y1)
        # return 0

        if x_max > x_min and y_max > y_min:
            return (x_max - x_min) * (y_max - y_min)
        return 0.

    def aignPressure(self, bin, pressure_map, total_area, weight):

        if len(pressure_map) == 0:
            return

        for index, area in pressure_map.items():
            new_weight = area / total_area * weight
            bin.items[index].pressure += new_weight

            # 递归更新间接子节点的压力值
            new_pressure_map = bin.items[index].parents
            self.aignPressure(bin, new_pressure_map, sum(list(new_pressure_map.values())), new_weight)

            # for new_index, ratio in bin.items[index].parents.items():
            #     bin.items[new_index].pressure += ratio * new_weight

    def refreshPressure(self, bin, item):
        pressure_map = {}
        # 1. 查找与item有直接接触的ib(bin.items的项)
        for index, ib in enumerate(bin.items[:-1]):
            # 1.1 计算ib与item的受力面积
            pressure_area = self.calculatePressureArea(ib, item)
            if pressure_area > 0:
                pressure_map[index] = pressure_area

        # 2. 递归更新间接父节点的pressure
        total_area = sum(list(pressure_map.values()))
        self.aignPressure(bin, pressure_map, total_area, float(bin.items[-1].weight))

        # 3. 更新parents和children
        for index, area in pressure_map.items():
            # 3.1 更新父节点的children
            bin.items[index].children.append(len(bin.items) - 1)
            # 3.2 更新本节点的parents
            bin.items[-1].parents[index] = area / total_area    # 按受力面积将当前item的重量分配到parents

        # 4. 判断每个item的pressure是否满足条件
        pressure_flag = []
        for ib in bin.items:
            # 4.1 判断ib上方有几层
            upper_layer = 0
            if ib.children:
                upper_layer = 1
                for c in ib.children:
                    if bin.items[c].children:
                        upper_layer = 2
                        break
            if upper_layer == 2:  # 上方堆叠了两层
                pressure_flag.append(0 < ib.pressure <= 600)
            elif upper_layer == 1:  # 上方堆叠了一层
                pressure_flag.append(0 < ib.pressure <= 1200)

        if all(pressure_flag):  # 所有的压力都满足条件
            return True
        else:
            # 撤回新添加的item
            return False


class Painter:

    def __init__(self,bins):
        ''' '''
        self.items = bins.items
        self.width = bins.width
        self.height = bins.height
        self.depth = bins.depth


    def _plotCube(self, ax, x, y, z, dx, dy, dz, color='red',mode=2,linewidth=1,text="",fontsize=15,alpha=0.5):
        """ Auxiliary function to plot a cube. code taken somewhere from the web.  """
        xx = [x, x, x+dx, x+dx, x]
        yy = [y, y+dy, y+dy, y, y]
        
        kwargs = {'alpha': 1, 'color': color,'linewidth':linewidth }
        if mode == 1 :
            ax.plot3D(xx, yy, [z]*5, **kwargs)
            ax.plot3D(xx, yy, [z+dz]*5, **kwargs)
            ax.plot3D([x, x], [y, y], [z, z+dz], **kwargs)
            ax.plot3D([x, x], [y+dy, y+dy], [z, z+dz], **kwargs)
            ax.plot3D([x+dx, x+dx], [y+dy, y+dy], [z, z+dz], **kwargs)
            ax.plot3D([x+dx, x+dx], [y, y], [z, z+dz], **kwargs)
        else :
            p = Rectangle((x,y),dx,dy,fc=color,ec='black',alpha = alpha)
            p2 = Rectangle((x,y),dx,dy,fc=color,ec='black',alpha = alpha)
            p3 = Rectangle((y,z),dy,dz,fc=color,ec='black',alpha = alpha)
            p4 = Rectangle((y,z),dy,dz,fc=color,ec='black',alpha = alpha)
            p5 = Rectangle((x,z),dx,dz,fc=color,ec='black',alpha = alpha)
            p6 = Rectangle((x,z),dx,dz,fc=color,ec='black',alpha = alpha)
            ax.add_patch(p)
            ax.add_patch(p2)
            ax.add_patch(p3)
            ax.add_patch(p4)
            ax.add_patch(p5)
            ax.add_patch(p6)
            
            if text != "":
                ax.text( (x+ dx/2), (y+ dy/2), (z+ dz/2), str(text),color='black', fontsize=fontsize, ha='center', va='center')

            art3d.pathpatch_2d_to_3d(p, z=z, zdir="z")
            art3d.pathpatch_2d_to_3d(p2, z=z+dz, zdir="z")
            art3d.pathpatch_2d_to_3d(p3, z=x, zdir="x")
            art3d.pathpatch_2d_to_3d(p4, z=x + dx, zdir="x")
            art3d.pathpatch_2d_to_3d(p5, z=y, zdir="y")
            art3d.pathpatch_2d_to_3d(p6, z=y + dy, zdir="y")


    def _plotCylinder(self, ax, x, y, z, dx, dy, dz, color='red',mode=2,text="",fontsize=10,alpha=0.2):
        """ Auxiliary function to plot a Cylinder  """
        # plot the two circles above and below the cylinder
        p = Circle((x+dx/2,y+dy/2),radius=dx/2,color=color,alpha=0.5)
        p2 = Circle((x+dx/2,y+dy/2),radius=dx/2,color=color,alpha=0.5)
        ax.add_patch(p)
        ax.add_patch(p2)
        art3d.pathpatch_2d_to_3d(p, z=z, zdir="z")
        art3d.pathpatch_2d_to_3d(p2, z=z+dz, zdir="z")
        # plot a circle in the middle of the cylinder
        center_z = np.linspace(0, dz, 10)
        theta = np.linspace(0, 2*np.pi, 10)
        theta_grid, z_grid=np.meshgrid(theta, center_z)
        x_grid = dx / 2 * np.cos(theta_grid) + x + dx / 2
        y_grid = dy / 2 * np.sin(theta_grid) + y + dy / 2
        z_grid = z_grid + z
        ax.plot_surface(x_grid, y_grid, z_grid,shade=False,fc=color,alpha=alpha,color=color)
        if text != "" :
            ax.text( (x+ dx/2), (y+ dy/2), (z+ dz/2), str(text),color='black', fontsize=fontsize, ha='center', va='center')

    def _plotCylinder_2(self, ax, x, y, z, dx, dy, dz, color='red',mode=2,text="",fontsize=10,alpha=0.2, elev=45, azim=40, pad_z=0):

        z = z + float(pad_z)

        padding = 15
        x_max, y_max, z_max = self.width + padding, self.height + padding, self.depth + padding

        # x, y = y, x
        # dx, dy = dy, dx

        """ Auxiliary function to plot a Cylinder  """
        # plot the two circles above and below the cylinder
        p = Circle((x+dx/2, z+dz/2), radius=dx/2)
        p2 = Circle((x+dx/2, z+dz/2), radius=dx/2)
        p.set_edgecolor('black')
        p.set_alpha(0.2)
        p.set_facecolor(color)
        p.set_alpha(0.2)
        # p.set_linestyle('dashed')

        p2.set_edgecolor('black')
        p2.set_alpha(0.2)
        p2.set_facecolor(color)
        p2.set_alpha(0.2)
        # p2.set_linestyle('dashed')

        ax.add_patch(p)
        ax.add_patch(p2)
        art3d.pathpatch_2d_to_3d(p, z=y, zdir="y")
        art3d.pathpatch_2d_to_3d(p2, z=y+dy, zdir="y")
        # plot a circle in the middle of the cylinder
        center_z = np.linspace(0, dy, 10)
        theta = np.linspace(0, 2*np.pi, 10)
        theta_grid, y_grid=np.meshgrid(theta, center_z)
        x_grid = dx / 2 * np.cos(theta_grid) + x + dx / 2
        z_grid = dz / 2 * np.sin(theta_grid) + z + dz / 2
        y_grid = y_grid + y

        # y_grid = dy / 2 * np.sin(theta_grid) + y + dy / 2
        # z_grid = z_grid + z


        # 调整视角
        ax.view_init(elev=elev,  # 仰角
                     azim=azim,  # 方位角
                     # vertical_axis='x'
                     )
        # ax.set_xlim3d(xmin=0, xmax=x_max)
        # ax.set_ylim3d(ymin=0, ymax=y_max)
        # ax.set_zlim3d(zmin=0, zmax=z_max)


        ax.plot_surface(x_grid, y_grid, z_grid,shade=False,fc=color,alpha=alpha,color=color)
        if text != "" :
            ax.text( (x+ dx/2), (y+ dy/2), (z+ dz/2), str(text),color='black', fontsize=fontsize, ha='center', va='center')


    def plotBoxAndItems(self,title="",alpha=0.2,write_num=False,fontsize=10):
        """ side effective. Plot the Bin and the items it contains. """
        fig = plt.figure()
        axGlob = plt.axes(projection='3d')
        
        # plot bin 
        self._plotCube(axGlob,0, 0, 0, float(self.width), float(self.height), float(self.depth),color='black',mode=1,linewidth=2,text="")

        counter = 0
        # fit rotation type
        for item in self.items:
            rt = item.rotation_type  
            x,y,z = item.position
            # [w,h,d] = item.getDimension()
            [w, h, d] = item.dimension
            color = item.color
            text= item.partno if write_num else ""

            if item.typeof == 'cube':
                 # plot item of cube
                self._plotCube(axGlob, float(x), float(y), float(z), float(w),float(h),float(d),color=color,mode=2,text=text,fontsize=fontsize,alpha=alpha)
            elif item.typeof == 'cylinder':
                # plot item of cylinder
                self._plotCylinder(axGlob, float(x), float(y), float(z), float(w),float(h),float(d),color=color,mode=2,text=text,fontsize=fontsize,alpha=alpha)
            
            counter = counter + 1  

        # 创建矩形图例
        legend_elements = [Rectangle((0, 0), 1, 1, fc='red'), Rectangle((0, 0), 1, 1, fc='blue'), Rectangle((0, 0), 1, 1, fc='yellow')]
        axGlob.legend(legend_elements, ['wooden', 'tray', 'carton'], loc='upper left')


        
        plt.title(title)
        self.setAxesEqual(axGlob)
        return plt


    def setAxesEqual(self,ax):
        '''Make axes of 3D plot have equal scale so that spheres appear as spheres,
        cubes as cubes, etc..  This is one possible solution to Matplotlib's
        ax.set_aspect('equal') and ax.axis('equal') not working for 3D.

        Input
        ax: a matplotlib axis, e.g., as output from plt.gca().'''
        x_limits = ax.get_xlim3d()
        y_limits = ax.get_ylim3d()
        z_limits = ax.get_zlim3d()

        x_range = abs(x_limits[1] - x_limits[0])
        x_middle = np.mean(x_limits)
        y_range = abs(y_limits[1] - y_limits[0])
        y_middle = np.mean(y_limits)
        z_range = abs(z_limits[1] - z_limits[0])
        z_middle = np.mean(z_limits)

        # The plot bounding box is a sphere in the sense of the infinity
        # norm, hence I call half the max range the plot radius.
        plot_radius = 0.5 * max([x_range, y_range, z_range])

        ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
        ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
        ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])

